#!/bin/sh
#
# Validation script for packages-other/manifest
#
# DO NOT RUN DIRECTLY ... this script is exec'd from check-vm -c
#

if [ -f /etc/pcp.conf ]
then
    . /etc/pcp.conf
else
    # punt
    #
    case `uname`
    in
	Darwin)
	    PCP_ECHO_PROG=echo
	    PCP_ECHO_N=
	    PCP_ECHO_C='\c'
	    ;;
	*)
	    PCP_ECHO_PROG=echo
	    PCP_ECHO_N=-n
	    PCP_ECHO_C=
	    ;;
    esac
fi

# Need directory where whatami script is located
#
home=`echo $0 | sed -e 's/\/*check-manifest$//'`
[ -z "$home" ] && home=`pwd`
if [ ! -d "$home" ]
then
    echo "$0: Botch: \$0=$0 -> bad \$home=$home ?"
    exit 1
fi
if [ ! -f "$home"/whatami ]
then
    echo "$0: Botch: cannot find $home/whatami"
    exit 1
fi

_usage()
{
    echo "Usage: $0 [-v] manifest-from-check-vm-c"
    exit 1
}

verbose=false
very_verbose=false
while getopts 'v?' p
do
    case "$p"
    in
	v)	if $verbose
		then
		    very_verbose=true
		else
		    verbose=true
		fi
		;;

	?)	_usage
		# NOTREACHED
    esac
done
shift `expr $OPTIND - 1`
[ $# -eq 1 ] || _usage

tmp="$1"
if [ ! -f $tmp.manifest ]
then
    echo "$0: Hand off botch: $tmp.manifest not found"
    exit 1
fi
if [ ! -f $tmp.unavail ]
then
    echo "$0: Hand off botch: $tmp.unavail not found"
    exit 1
fi

_cleanup()
{
    if $very_verbose
    then
	echo "Temporary files preserved: `echo $tmp.*`"
    else
	rm -f $tmp.*
    fi
}

sts=1
trap "_cleanup; exit \$sts" 0 1 2 3 15

if $very_verbose
then
    # clean up stuff left over from any previous runs, but be careful
    # about the ones check-vm has just set up
    #
    for suff in manifest unavail allpkgs
    do
	[ -f $tmp.$suff ] && mv $tmp.$suff $tmp-$suff
    done
    rm -f $tmp.*
    for suff in manifest unavail allpkgs
    do
	[ -f $tmp-$suff ] && mv $tmp-$suff $tmp.$suff
    done
fi

# $pkgtool tells us what family of packaging tools we need
# to use to map a pathname to an installed package name
# $searchtool tells us how to search for packages by name
# (including uninstalled packages)
#
# if check-vm was was able to produce a list of all available packages
# (installed and not installed), then the resulting list (package name,
# no versions) will be in $tmp.allpkgs and set $searchtool here to
# "cached"
#
pgktool=''
searchtool=''
[ -f $tmp.allpkgs ] && searchtool=cached
$home/whatami >$tmp.tmp
case "`sed <$tmp.tmp -e 's/  */ /g' -e 's/ \[.*//' | cut -d ' ' -f 4-`"
in
    *Ubuntu*|*Debian*|*LinuxMint*)
	pkgtool=dpkg
	;;

    *RHEL*|*Fedora*|*CentOS*|*openSUSE*|*SUSE\ SLES*)
	pkgtool=rpm
	if which zypper >/dev/null 2>&1
	then
	    searchtool=zypper
	fi
	;;

    *NetBSD*)
	pkgtool=pkgin
	searchtool=pkgin
	;;

    *FreeBSD*)
	pkgtool=F_pkg		# FreeBSD version of pkg(1)
	searchtool=F_pkg
	;;

    *OpenBSD*)
	pkgtool=pkg_add
	;;

    *Gentoo*)
	pkgtool=emerge
	searchtool=equery
	;;

    *Darwin*)
	pkgtool=brew
	searchtool=brew
	;;

    *OpenIndiana*)
	pkgtool=S_pkg		# OpenIndiana version of pkg(1)
	searchtool=S_pkg
	;;

    *Slackware*)
	pkgtool=slackpkg
	;;

    *Arch\ Linux*)
	pkgtool=pacman
	searchtool=pacman
	;;

esac
if [ -z "$pkgtool" ]
then
    echo "$0: Botch: don't recognize pkgtool for: `cat $tmp.tmp`"
    exit
fi

_undot()
{
    if [ -f $tmp.dots ]
    then
	echo
	rm -f $tmp.dots
    fi
}

_path_to_pkg()
{
    # Try other packaging options (Perl or Python) first ...
    #
    for info in $2
    do
	case "$info"
	in
	    "cpan("*")")
		module=`echo "$info" | sed -e 's/.*(//' -e 's/)//'`
		if [ -z "$module" ]
		then
		    echo "Botch: failed to get $tool module from \"$info\""
		    touch $tmp.abort
		    return
		fi
		if which cpan >/dev/null
		then
		    if [ ! -f $tmp.perlmodules ]
		    then
			sudo cpan -l 2>/dev/null \
			| sed -e 's/[ 	].*//' \
			| LC_COLLATE=POSIX sort >$tmp.perlmodules
			if $very_verbose
			then
			    echo >&2 "cpan -l -> `wc -l <$tmp.perlmodules | sed -e 's/  *//g'` lines"
			    cat -n $tmp.perlmodules | head >&2 -3
			    echo >&2 "  ..."
			    cat -n $tmp.perlmodules | tail >&2 -3
			fi
		    fi
		    if [ -s $tmp.perlmodules ]
		    then
			if grep "^$module\$" $tmp.perlmodules >/dev/null
			then
			    # found it!
			    echo "$info"
			    $very_verbose && echo >&2 "found cpan info for $info"
			    return
			fi
		    else
			# either no modules installed by cpan, or cpan is
			# too old to support the -l option ... since we
			# cannot be sure, just pretent
			echo "$info"
			$very_verbose && echo >&2 "assume cpan install for $info"
			return

		    fi
		elif [ ! -f $tmp.cpan.warn ]
		then
		    echo >&2 "Warning: cpan not installed, cannot check Perl modules for cpan()" | tee $tmp.cpan.warn
		fi
		;;
	    "pip("*")"|"pip3("*")")
		tool=`echo "$info" | sed -e 's/(.*//'`
		module=`echo "$info" | sed -e 's/.*(//' -e 's/)//'`
		if [ -z "$tool" ]
		then
		    echo "Botch: failed to get tool from \"$info\""
		    touch $tmp.abort
		    return
		fi
		if [ -z "$module" ]
		then
		    echo "Botch: failed to get $tool module from \"$info\""
		    touch $tmp.abort
		    return
		fi
		if `which "$tool"` >/dev/null
		then
		    $tool show -f "$module" >$tmp.tmp
		    match=`echo "$1" | sed -e 's;.*-packages/;;'`
		    if grep " $match\$" $tmp.tmp >/dev/null
		    then
			# found it!
			echo "$info"
			return
		    fi
		fi
		;;
	esac
    done

    case "$pkgtool"
    in
	dpkg)
	    # expecting output like
	    # lm-sensors: /usr/bin/sensors
	    #
	    dpkg-query -S "$1" 2>/dev/null | sed -e 's/:.*//' >$tmp.tmp
	    # if not found in any package, try realpath() alternative
	    #
	    if [ -s $tmp.tmp ]
	    then
		:
	    elif which realpath >/dev/null 2>&1
	    then
		_target=`realpath "$1"`
		if [ "$_target" != "$1" ]
		then
		    dpkg-query -S "$_target" 2>/dev/null | sed -e 's/:.*//' >$tmp.tmp
		fi
	    fi
	    if [ -s $tmp.tmp ]
	    then
		:
	    else
		# very odd failure on test-ubuntu2004-direct in CI where
		# /bin is a symlink -> usr/bin, so targets installed from
		# packages intended for /bin actually end up in /usr/bin,
		# specifically bash, sed, grep and mktemp
		#
		case "$1"
		in
		    /usr/bin/*)
			_target=`echo "$1" | sed -e 's;^/usr/bin/;/bin/;'`
			dpkg-query -S "$_target" 2>/dev/null | sed -e 's/:.*//' >$tmp.tmp
			;;
		esac
		if [ ! -s $tmp.tmp ]
		then
		    # still no dice, output some helpful diags
		    #
		    echo >&2 "+++ Bear Trap +++"
	 	    ls >&2 -ild /usr/bin /bin "$1" "`echo "$1" | sed -e 's;/usr;;'`"
		    which realpath >/dev/null 2>/&1 && echo >&2 "realpath -> `realpath "$1"`"
		    echo >&2 "_target=$_target"
		fi
	    fi
	    [ -s $tmp.tmp ] && cat $tmp.tmp
	    ;;

	rpm)
	    # expecting output like
	    # lm_sensors-3.4.0-8.fc27.x86_64
	    #
	    rpm -qf "$1" 2>/dev/null | sed -e '/is not owned by any/d' -e 's/-[0-9].*//' >$tmp.tmp
	    # if not found in any package, try realpath() alternative
	    #
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    elif which realpath >/dev/null 2>&1
	    then
		_target=`realpath "$1"`
		if [ "$_target" != "$1" ]
		then
		    rpm -qf "$_target" 2>/dev/null | sed -e '/is not owned by any/d' -e 's/-[0-9].*//'
		fi
	    fi
	    ;;

	pkgin)
	    # expecting output like
	    # bash-4.4.18<version babble>
	    #
	    pkg_info -Fe "$1" 2>/dev/null \
	    | sed >$tmp.tmp -e 's/-[0-9].*//'
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    fi
	    ;;

	F_pkg)
	    # expecting output like
	    # /usr/local/bin/bash was installed by package bash-4.4.19
	    #
	    pkg which "$1" 2>/dev/null \
	    | sed -n >$tmp.tmp \
		-e '/was installed by package/{
s/.*was installed by package //
s/-[0-9].*//
p
}'
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    fi
	    ;;

	S_pkg)
	    # expecting output like
	    # shell/bash
	    #
	    # need to strip leading / from path before searching
	    # also more than one package can install the same binary (!)
	    # e.g. g++ in developer/gcc-6 and developer/gcc-7, in this
	    # case, pick the first one (arbitrarily)
	    #
	    pkg contents -H -o pkg.name -a path=`echo "$1" | sed -e 's;^/;;' -e 1q` 2>/dev/null
	    ;;

	pkg_add)
	    # expecting output like
	    # bash-4.4.23:shells/bash:/usr/local/bin/bash
	    # bash-4.4.23:shells/bash:/usr/local/bin/bashbug
	    # bashunit-20140327:devel/bashunit:/usr/local/bin/bashunit.bash
	    #
	    # version is optional, so need to strip stuff after : first
	    #
	    # no apparent way to pass glob chars thru, hence the grep
	    # at the end
	    #
	    pkg_locate "$1" 2>/dev/null \
	    | grep ":$1\$" \
	    | sed -e 's/:.*//' -e 's/-[0-9].*//' \
	    | LC_COLLATE=POSIX sort \
	    | uniq \
	    | ( tr '\012' '|'; echo ) \
	    | sed -e 's/|$//' >$tmp.tmp
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    fi
	    ;;

	emerge)
	    # expecting output like
	    # sys-apps/lm_sensors-3.4.0_p20170901
	    # (don't be fooled by interactive output, when output goes
	    # to a file, a different format is used!)
	    #
	    equery belongs "$1" 2>/dev/null \
	    | sed -e '/Searching for/d' -e 's/-[0-9].*//' >$tmp.tmp
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    fi
	    ;;

	slackpkg)
	    # expecting output like
	    # /var/log/packages/bash-4.3.048-x86_64-1_slack14.2
	    #
	    # Need to strip leading / from path before searching.
	    # Apply realpath to unwind any symlinks.
	    # Try exact match first, else prefix match with *.new suffix
	    # (this was needed for /bin/bash where the packaed file is
	    # /bin/bash4.new and some post-install magic makes the real
	    # /usr/bin/bash and the symlink /bin/bash).  Ditto for
	    # /etc/httpd/httpd.conf.new (packaged) vs
	    # /etc/httpd/httpd.conf (installed).
	    #
	    _path=`realpath "$1"`
	    if $very_verbose
	    then
		echo >&2 "_path_to_pkg: arg=\"$1\" _path=\"$_path\""
	    fi
	    for suff in '' '.*\.new'
	    do
		grep -rl `echo "$_path$suff" | sed -e 's;^/;^;' -e 's/$/\$/'` /var/log/packages \
		| sed >$tmp.tmp \
		    -e 's,/var/log/packages/,,' \
		    -e 's/-[0-9].*//' \
		# end
		if [ -s $tmp.tmp ]
		then
		    cat $tmp.tmp
		    return
		fi
	    done
	    ;;

	pacman)
	    # expecting output like
	    # /usr/bin/bash is owned by core/bash 4.4.023-1
	    #
	    if $very_verbose
	    then
		echo >&2 "_path_to_pkg: arg=\"$1\""
	    fi
	    pacman -Qo "$1" 2>/dev/null | sed -e 's/.* is owned by //' -e 's/ .*//' >$tmp.tmp
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    fi
	    ;;

	brew)
	    # for commands expect output from ls like
	    # lrwxr-xr-x  1 kenj  admin  28 29 Nov 06:14 /usr/local/bin/nmap -> ../Cellar/nmap/7.70/bin/nmap
	    #
	    ls -l "$1" 2>/dev/null \
	    | sed >$tmp.tmp -n -e '/ -> .*\/Cellar\//{
s;.* -> .*/Cellar/;;
s;/.*;;
p
}'
	    if [ -s $tmp.tmp ]
	    then
		cat $tmp.tmp
	    else
		# for non-commands, it is all a bit messier ...
		# - use last part of pathname from $1
		# - find $(brew --cellar) -name ... for candidates
		# - strip $(brew --cellar) prefix
		# - extract formula and path
		# - try to tail match paths
		#
		cellar=`brew --cellar`
		if [ ! -d "$cellar" ]
		then
		    echo "Botch: brew cellar \"$cellar\" does not exist"
		    touch $tmp.abort
		    return
		fi
		base=`echo "$1" | sed -e 's/.*\///'`
		find $cellar -name $base \
		| sed -e "s;$cellar/;;" -e 's;/[0-9./]*; ;' \
		| while read formula path
		do
		    if echo "$1" | grep "$path\$" >/dev/null
		    then
			echo $formula
			break
		    fi
		done
	    fi
	    ;;

	*)
	    echo "_path_to_pkg: cannot handle $pkgtool packaging yet"
	    touch $tmp.abort
	    ;;
    esac
    if $very_verbose
    then
	[ -s $tmp.tmp ] || echo >&2 "no pkg for $info"
    fi
}

# Deal with some known metapackage equivalences.
#
# Usage: _match_pkg pkg-from-pkgtool pkg-from-manifest
#
# pkg-from-pkgtool may contain alternates (separated by |) if the target
# file is delivered in more than one package, e.g. on OpenBSD
#
_match_pkg()
{
    rm -f $tmp.match
    for xpect in `echo "$1" | sed -e 's/|/ /g'`
    do
	case $pkgtool
	in
	    dpkg)
		    case "$xpect"
		    in
			libpython2.[0-9]-dev)	# e.g. libpython2.7-dev
			    xpect=libpython-dev
			    ;;
			libpython2.[0-9]-stdlib)	# e.g. libpython2.7-stdlib
			    xpect=libpython-stdlib
			    ;;
			libpython3.[0-9]-dev|libpython.3.[0-9]m)	# e.g. libpython3.6-dev
			    xpect=libpython3-dev
			    ;;
			libpython3.[0-9]-stdlib)	# e.g. libpython3.6-stdlib
			    xpect=libpython3-stdlib
			    ;;
			libuv[0-9]*.[0-9]*-dev)	# e.g. libuv0.10-dev 
			    xpect=libuv-dev
			    ;;
			python2.[0-9])		# e.g. python2.7
			    xpect=python
			    ;;
			python2.[0-9]-dev)		# e.g. python2.7-dev
			    xpect=python-dev
			    ;;
		    esac
		    ;;
	    rpm)
		    case "$xpect"
		    in
			postgresql[0-9]*)		# e.g. postgresql10
			    xpect=postgresql
			    ;;
		    esac
		    ;;

	    pkgin)
		    case "$xpect"
		    in
			postgresql[0-9]*-client)	# e.g. postgresql95-client
			    xpect=postgresql-client
			    ;;
		    esac
		    ;;

	    F_pkg)
		    case "$xpect"
		    in
			postgresql[0-9]*-client)	# e.g. postgresql95-client
			    xpect=postgresql-client
			    ;;
		    esac
		    ;;

	    pkg_add)
		    case "$xpect"
		    in
			base6[0-9]|comp6[0-9])
			    xpect=base
			    ;;
			xshare6[0-9])
			    xpect=xshare
			    ;;
		    esac
		    ;;

	    emerge)
		    # try full package name first
		    #
		    if [ "$xpect" = "$2" ]
		    then
			touch $tmp.match
			break
		    fi
		    # names like foo/bar are expected from equery, but
		    # often bar is unique and the foo/ part is not needed
		    # so test for trailing match first
		    #
		    basename=`echo "$xpect" | sed -e 's/.*\///'`
		    if [ "$basename" = "$2" ]
		    then
			touch $tmp.match
			break
		    fi
		    ;;

	    pacman)
		    if [ "$xpect" != "$2" ]
		    then
			# may have module/package from manifest,
			# but only package from pacman -Qo
			#
			__pkg=`echo "$2" | sed -e 's;.*/;;'`
			[ "$xpect" = "$__pkg" ] && xpect="$2"
		    fi
		    ;;

	esac
	if [ "$xpect" = "$2" ]
	then
	    touch $tmp.match
	    break
	fi
    done

    if [ -f $tmp.match ]
    then
	return 0
    else
	return 1
    fi
}

# Hunt for a target in the packages.
# On entry:
# $target is a list of alternate targets (separated by |)
# $pkginfo is the list of alternate packages (separated by
# a space) that we think one of the targets is to be found in
#  ... this is the [...] part of the manifest with noise words
#  removed
#
_hunt()
{

    rm -f $tmp.msg $tmp.ok
    for file in `echo "$target" | sed -e 's/|/ /g'`
    do
	if [ -f $tmp.allpkgs ]
	then
	    # we have a cache of available packages ... check all the
	    # ones remaining really do exist
	    #
	    for pkg in $pkginfo
	    do
		case "$pkg"
		in
		    base|xshare)	# no packaging for these ones
			    ;;

		    'cpan('*')')	# skip cpan as we can't check 'em here
			    ;;

		    'pip3('*')')	# ditto
			    ;;

		    *'::'*)		# raw Perl module name, nothing to see here
			    ;;

		    ?:[0-9]*)		# unknown
			    _undot
			    echo "$line: package \"?\" unknown for exec target \"$target\""
			    ;;
		    *)
			    if grep "^$pkg\$" $tmp.allpkgs >/dev/null
			    then
				:
			    else
				_undot
				echo "$line: package \"$pkg\" appears to be unavailable"
			    fi
			    ;;
		esac
	    done
	fi

	case "$file"
	in
	    /*)
		if [ -f "$file" -o -d "$file" ]
		then
		    :
		else
		    echo "$line: file/dir target \"$file\" not found" >>$tmp.msg
		    file=''
		fi
		;;
	    *)
		which=`which $file 2>/dev/null`
		if [ -z "$which" -o ! -x "$which" ]
		then
		    echo "$line: exec target \"$file\" not found or not executable" >>$tmp.msg
		    file=''
		else
		    file="$which"
		fi
		;;
	esac
	if [ -n "$file" ]
	then
	    pkg=`_path_to_pkg "$file" "$pkginfo"`
	    if [ -f $tmp.abort ]
	    then
		echo "$pkg"
		exit
	    fi
	    $very_verbose && echo >&2 "file=\"$file\" from pkg=\"$pkg\""
	    if [ -z "$pkg" ]
	    then
		if echo " $pkginfo " | grep '::' >/dev/null
		then
		    # if a Perl module is specified as a possible provider
		    # then this is probably a cpan install, but cpan is too
		    # old to provide installed modules via cpan -l
		    #
		    $verbose && echo >&2 "$line: OK target $file assumed to be from cpan install"
		    :
		elif echo " $pkginfo " | grep ' base ' >/dev/null
		then
		    # if "base" is specified as a possible provider (which
		    # may be from a line that contains no provider info,
		    # see above) and the file exists (or we wouldn't be here)
		    # then that is OK if it is not installed by the package
		    # manager
		    #
		    $verbose && echo >&2 "$line: OK target $file assumed to be from base system install"
		    :
		else
		    echo "$line: target \"$file\" not installed from any package" >>$tmp.msg
		fi
	    else
		rm -f $tmp.ok
		for spec in $pkginfo
		do
		    if _match_pkg "$pkg" "$spec"
		    then
			$verbose && echo >&2 "$line: OK target $file found in $spec"
			touch $tmp.ok
		    else
			echo "$line: target \"$file\" in package \"$pkg\" not \"$spec\"" >>$tmp.msg
		    fi
		done
		[ -f $tmp.ok ] && break
	    fi
	fi
    done
    if [ ! -f $tmp.ok -a -f $tmp.msg ]
    then
	_undot
	cat $tmp.msg
    fi
}

# all the places where Perl bits might get installed .. geez!
#
_perl_dirs=''
for dir in /usr/lib/perl /usr/lib/perl5 /usr/lib*/perl /usr/lib*/perl5 /usr/lib/*/perl /usr/lib/*/perl5 /usr/share/perl /usr/share/perl5 /usr/pkg/lib/perl5 /usr/local/share/perl5 /usr/local/lib*/perl5/ /usr/local/share/perl /usr/perl5 /Library/Perl/5.* /System/Library/Perl/5.* /System/Library/Perl/Extras/5.*
do
    [ -d "$dir" ] && _perl_dirs="$_perl_dirs $dir"
done

# strip comments, empty (only :<lineno>) and N/A lines and skip lines
#
sed <$tmp.manifest \
    -e 's/#.*\(:[0-9][0-9]*\)$/\1/' \
    -e '/^[ 	]*:[0-9][0-9]*$/d' \
    -e '/^[ 	]*$/d' \
    -e '/\[N\/A/d' \
    -e '/\[SKIP]/d' \
    -e '/\[SKIP on host/d' >$tmp.tmp
mv $tmp.tmp $tmp.manifest

$PCP_ECHO_PROG $PCP_ECHO_N "Checking the `wc -l <$tmp.manifest | sed -e 's/ //g'` manifest entries for `hostname -s` ""$PCP_ECHO_C"
if $verbose || $very_verbose
then
    $PCP_ECHO_PROG "..."
else
    _undot
    echo
fi

cat $tmp.manifest \
| while read guard target extra
do
    if $very_verbose
    then
	echo >&2 "check N/A for \"$guard\""
    elif $verbose
    then
	:
    else
	# may be slow, so emit dots for progress ...
	#
	$PCP_ECHO_PROG >&2 $PCP_ECHO_N ."$PCP_ECHO_C"
	touch $tmp.dots
    fi
    # most lines have no guard, but some do, e.g.
    # !pkg?   egcc|gcc        [gcc]
    # weird?  foobar          [foobar-dev]
    # so do the guard logic to decide if this line should
    # be processed or skipped
    #
    case "$guard"
    in
	!*\?)	# negated guard
	    _guard=`echo "$guard" | sed -e 's/^!//' -e 's/?$//'`
	    if which $_guard >/dev/null 2>&1
	    then
		continue
	    fi
	    ;;
	*\?)	# guard
	    _guard=`echo "$guard" | sed -e 's/?$//'`
	    if which $_guard >/dev/null 2>&1
	    then
		:
	    else
		continue
	    fi
	    ;;
	*)	# just regular lines, there is no "guard"
	    extra="$target $extra"
	    target="$guard"
	    ;;
    esac

    line=`echo $extra | sed -e 's/.*:\([0-9][0-9]*\)$/\1/'`
    # reduce things like "[lm-sensors (QA optional)]:1189" down to
    # "lm-sensors"
    # and "[perl-Spreadsheet-ReadSXC or cpan(Spreadsheet::ReadSXC) (QA optional)]:601
    # down to "perl-Spreadsheet-ReadSXC cpan(Spreadsheet::ReadSXC)"
    # and cull the [N/A...] lines
    #
    pkginfo=`echo "$extra" \
	| sed \
	    -e 's/ :[0-9][0-9]*$//' \
	    -e 's/#.*//' \
	    -e 's/^\[//' \
	    -e 's/] *$//' \
	    -e 's/([^)]* optional)//' \
	    -e 's/base [^ ][^ ]* install/base/' \
	    -e 's/ or / /g' \
	    -e 's/  */ /g' \
	    -e 's/^ //' \
	    -e 's/ $//'`
    # we have some manifest lines for really common commands where
    # there is no [pkginfo], so punt on the package name being the same
    # as the target name else it is part of a base system install
    #
    [ -z "$pkginfo" ] && pkginfo="$target base"
    # unknown ones warrant a warning
    #
    if [ "$pkginfo" = "?" ]
    then
	_undot
	echo "$line: Warning: package for \"$target\" is not known"
    fi
    $very_verbose && echo >&2 target=\"$target\" extra=\"$extra\" pkginfo=\"$pkginfo\"

    case "$target"
    in
	*::)
	    # special case Perl, no module name
	    _perl=`echo "$target" | sed -e 's/:://'`
	    _target=`find $_perl_dirs -name "$_perl.*" -print 2>/dev/null \
		     | tee $tmp.debug \
	             | egrep "/$_perl.(pm|so|dylib|bs)$" \
		     | ( tr '\012' '|'; echo ) \
		     | sed -e 's/|$//'`
	    if [ -z "$_target" ]
	    then
		$very_verbose && echo >&2 "... looking for $_perl.* from candidates: `cat $tmp.debug`"
		if [ "`expr "$pkginfo" : "cpan(.*"`" -gt 0 ]
		then
		    # say nothing if cpan() is the only package option
		    :
		else
		    _undot
		    echo "$line: Perl source \"$target\" not found"
		fi
	    else
		$very_verbose && echo >&2 "$line: Perl $target -> $_target"
		target="$_target"
		_hunt
	    fi
	    ;;

	*::*::*)
	    # special case Perl, with module and submodule name
	    _module=`echo "$target" | sed -e 's/::.*//'`
	    _submodule=`echo "$target" | sed -e 's/[^:]*::\([^:][^:]*\)::.*/\1/'`
	    _perl=`echo "$target" | sed -e 's/.*:://'`
	    _target=`find $_perl_dirs -name "$_perl.*" -print 2>/dev/null \
		     | tee $tmp.debug \
	             | egrep "/$_module/($_submodule|$_submodule/$_perl)/$_perl.(pm|so|dylib|bs)$" \
		     | ( tr '\012' '|'; echo ) \
		     | sed -e 's/|$//'`
	    if [ -z "$_target" ]
	    then
		$very_verbose && echo >&2 "... looking for $_module/($_submodule|$_submodule/$_perl)/$_perl.* from candidates: `cat $tmp.debug`"
		if [ "`expr "$pkginfo" : "cpan(.*"`" -gt 0 ]
		then
		    # say nothing if cpan() is the only package option
		    :
		else
		    _undot
		    echo "$line: Perl source \"$target\" not found"
		fi
	    else
		$very_verbose && echo >&2 "$line: Perl $target -> $_target"
		target="$_target"
		_hunt
	    fi
	    ;;

	*::*)
	    # normal case Perl, with module name
	    _module=`echo "$target" | sed -e 's/::.*//'`
	    _perl=`echo "$target" | sed -e 's/.*:://'`
	    _target=`find $_perl_dirs -name "$_perl.*" -print 2>/dev/null \
		     | tee $tmp.debug \
	             | egrep "/$_module/$_perl.(pm|so|dylib|bs)$" \
		     | ( tr '\012' '|'; echo ) \
		     | sed -e 's/|$//'`
	    if [ -z "$_target" ]
	    then
		$very_verbose && echo >&2 "... looking for $_module/$_perl.* from candidates: `cat $tmp.debug`"
		if [ "`expr "$pkginfo" : "cpan(.*"`" -gt 0 ]
		then
		    # say nothing if cpan() is the only package option
		    :
		else
		    _undot
		    echo "$line: Perl source \"$target\" not found"
		fi
	    else
		$very_verbose && echo >&2 "$line: Perl $target -> $_target"
		target="$_target"
		_hunt
	    fi
	    ;;

	*)  # file, directory or executable tests, separated by |
	    # if we can find one of the alternatives we're good
	    #
	    _hunt
	    ;;
    esac

done
_undot

# check the N/A packages list for this platform
#
# first, list all currently installed packages ...
#
case "$pkgtool"
in
    dpkg)
	dpkg-query -W -f '${package}\n'
	;;

    rpm)
	rpm -qa --qf '%{NAME}\n'
	;;

    pkgin)
	pkgin list | sed -e 's/-[0-9].*//'
	;;

    F_pkg)
	pkg info -a | sed -e 's/-[0-9].*//'
	;;

    S_pkg)
	pkg list -H | sed -e 's/ .*//'
	;;

    pkg_add)
	pkg_info -a | sed -e 's/-[0-9].*//'
	;;

    emerge)
	equery list '*' | sed -e 's/.*].*] //' -e 's/-[0-9].*//'
	;;

    slackpkg)
	ls /var/log/packages | sed -e 's/-[0-9].*//'
	;;

    pacman)
	pacman -Q -i | sed -n -e '/^Name /s/.* : //p'
	;;

    brew)
	echo >&2 "TODO: no list all packages method for brew"
	;;

    *)
	echo >&2 "Botch: no list all packages method for $pkgtool"
	;;
esac >$tmp.installed

if [ -s $tmp.unavail ]
then
    rm -f $tmp.botch $tmp.pkginpkgs
    if grep '[[*^$]' $tmp.unavail >/dev/null
    then
	# remove lines with grep patterns ... it is just too hard to
	# know what to check for ...
	#
	grep -v '[[*^$]' <$tmp.unavail >$tmp.tmp
	mv $tmp.tmp $tmp.unavail
    fi
    $PCP_ECHO_PROG $PCP_ECHO_N "Checking the `wc -l <$tmp.unavail | sed -e 's/ //g'` N/A packages for `hostname -s` ""$PCP_ECHO_C"
    if $verbose
    then
	$PCP_ECHO_PROG "..."
    else
	_undot
	echo
    fi
    cat $tmp.unavail \
    | while read pkg 
    do
	if $verbose
	then
	    echo >&2 "check N/A for \"$pkg\""
	else
	    # may be slow, so emit dots for progress ...
	    #
	    $PCP_ECHO_PROG >&2 $PCP_ECHO_N ."$PCP_ECHO_C"
	    touch $tmp.dots
	fi
	rm -f $tmp.avail
	case "$searchtool"
	in
	    cached)
		if grep "^$pkg\$" $tmp.allpkgs >/dev/null
		then
		    touch $tmp.avail
		fi
		;;

	    dpkg-query)
		if dpkg-query -l "$pkg" >$tmp.out 2>&1
		then
		    if grep "^un *$pkg " $tmp.out >/dev/null
		    then
			:
		    else
			touch $tmp.avail
			$verbose && cat >&2 $tmp.out
		    fi
		fi
		;;

	    yum)
		# For example: search bash ->
		# bash.x86_64 : The GNU Bourne Again shell
		# make package name match up to the .
		#
		prefix=`echo "$pkg" | sed -e 's/[. ].*//'`
		if yum search "$pkg" 2>&1 | grep "^$prefix" >$tmp.out 2>&1
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    dnf)
		# For example: search bash ->
		# bash.x86_64 : The GNU Bourne Again shell
		# make package name match up to the .
		#
		prefix=`echo "$pkg" | sed -e 's/[. ].*//'`
		if dnf search "$pkg" 2>&1 | grep "^$prefix" >$tmp.out 2>&1
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    zypper)
		# make package name match up to the first digit or hypen
		#
		prefix=`echo "$pkg" | sed -e 's/^\([^0-9-]*\).*/\1/'`
		if zypper search -t package "$pkg" | grep "| $prefix" >$tmp.out 2>&1
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    pkgin)
		[ ! -f $tmp.pkginpkgs ] && pkgin avail >$tmp.pkginpkgs
		if grep "^$pkg-" $tmp.pkginpkgs >$tmp.out 2>&1
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    F_pkg)
		# For example: search bash ->
		# bash-4.4.23                    GNU Project's Bourne Again SHell
		# make package name match up to the -<digit>
		#
		prefix=`echo "$pkg" | sed -e 's/-[0-9].*//'`
		if pkg search "$pkg" 2>&1 | grep "^$prefix" >$tmp.out 2>&1
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    pkg_info)
		# For example: search bash (installed) ->
		# Information for inst:bash-4.4.23
		# 		   search GraphicsMagic (not installed) ->
		# Information for https://mirror.aarnet.edu.au/pub/OpenBSD/6.4/packages/amd64/GraphicsMagick-1.3.30.tgz
		#
		# Using pkg_info becasuse pkg_locate matches on path,
		# not package name
		#
		if pkg_info "$pkg" 2>&1 | grep '^Information ' >$tmp.out
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    equery)
		# For example: equery meta bash ->
		# ...
		# Location:    /usr/portage/app-shells/bash
		#
		if equery meta "$pkg" 2>&1 | grep '^Location:' >$tmp.out
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    slackpkg)
		# For example: slackpkg search bash ->
		# ...
		# [ installed ] - bash-4.3.048-x86_64-1_slack14.2
		# [uninstalled] - bash-completion-2.2-noarch-3
		#
		if slackpkg search "$pkg" 2>&1 | grep '^\[uninstalled]' >$tmp.out
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    pacman)
		# For example: pacman -Sl | grep " bash " ->
		# core bash 4.4.023-1 [installed]
		#
		if pacman -Sl | grep " $pkg " >$tmp.out
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;


	    brew)
		# For example: brew info bash ->
		# ...
		# bash: stable 4.4.23 (bottled), HEAD
		#
		if brew info "$pkg" 2>&1 | grep "^$pkg:" >$tmp.out
		then
		    touch $tmp.avail
		    $verbose && cat >&2 $tmp.out
		fi
		;;

	    *)
		_undot
		echo "_hunt: Botch: no rule for searchtool=$searchtool"
		touch $tmp.botch
		break
		;;
	esac
	if [ -f $tmp.avail ]
	then
	    _undot
	    $verbose && cat >&2 $tmp.out
	    if grep "^$pkg\$" $tmp.installed >/dev/null
	    then
		echo "looks like package \"$pkg\" is already installed"
	    else
		echo "looks like package \"$pkg\" may be available"
	    fi
	fi
	[ -f $tmp.botch ] && break
    done

    _undot
fi

sts=0
exit
